﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Xml.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.IO;
using System.Xml;
using System.Text;
using System.Collections;

namespace nwrole.lib
{
    public class PropertyList : IDictionary<string, object>
    {
        private Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

        public PropertyList ()
        { }

        public PropertyList (Stream inputStream)
        {
            XmlDocument document = new XmlDocument();
            document.Load(inputStream);
            LoadDocument(document);
        }

        public PropertyList (string path)
        {
            XmlDocument document = new XmlDocument();
            document.Load(path);
            LoadDocument(document);
        }

        public static PropertyList LoadData (string xml)
        {
            PropertyList plist = new PropertyList();
            XmlDocument document = new XmlDocument();
            document.LoadXml(xml);
            plist.LoadDocument(document);
            return plist;
        }

        #region Saving
        public string ToXml ()
        {
            XmlDocument document = GetDocument();
            StringWriter stringWriter = new StringWriter();
            XmlTextWriter xmlWriter = new XmlTextWriter(stringWriter);
            //xmlWriter.Formatting = Formatting.Indented;
            xmlWriter.Formatting = Formatting.None;
            document.WriteTo(xmlWriter);
            return stringWriter.ToString();
        } 

        public void Save (string path)
        {
            XmlDocument document = this.GetDocument();
            document.Save(path);
        }

        public void Save (Stream stream)
        {
            XmlDocument document = this.GetDocument();
            document.Save(stream);
        }

        private XmlDocument GetDocument ()
        {
            XmlDocument document = new XmlDocument();

            document.AppendChild(document.CreateXmlDeclaration("1.0", "UTF-8", null));
            //document.AppendChild(document.CreateDocumentType("plist", "-//Apple//DTD PLIST 1.0//EN", "http://www.apple.com/DTDs/PropertyList-1.0.dtd", null));

            XmlElement root = document.CreateElement("plist");
            XmlAttribute attr = document.CreateAttribute("version");
            attr.Value = "1.0";
            root.Attributes.Append(attr);
            document.AppendChild(root);

            root.AppendChild(GetDictionary(dictionary, document));

            return document;
        }

        private XmlElement GetElement (object value, XmlDocument document)
        {
            if (value == null)
                return document.CreateElement("null");
            Type type = value.GetType();
            if (typeof(IDictionary).IsAssignableFrom(type))
                return GetDictionary((IDictionary)value, document);
            else if (typeof(IList).IsAssignableFrom(type) || type.IsArray)
                return GetList((IEnumerable)value, document);
            else if (type == typeof(bool))
                return GetBoolean((bool)value, document);
            else if (type == typeof(int) || type == typeof(Int16) || type == typeof(Int32) || type == typeof(Int64))
                return GetInteger((int)value, document);
            else if (type == typeof(float) || type == typeof(decimal) || type == typeof(double))
                return GetReal((float)value, document);
            else if (type == typeof(DateTime))
                return GetDate((DateTime)value, document);
            else if (type == typeof(TimeSpan))
                return GetTimeSpan((TimeSpan)value, document);
            return GetString(Convert.ToString(value), document);
        }

        private XmlElement GetDictionary (IDictionary dictionary, XmlDocument document)
        {
            XmlElement dict = document.CreateElement("dict");
            foreach (DictionaryEntry entry in dictionary)
            {
                XmlElement key = document.CreateElement("key");
                key.InnerText = Convert.ToString(entry.Key);
                dict.AppendChild(key);
                dict.AppendChild(GetElement(entry.Value, document));
            }
            return dict;
        }

        private XmlElement GetList (IEnumerable list, XmlDocument document)
        {
            XmlElement array = document.CreateElement("array");
            foreach (object value in list)
                array.AppendChild(GetElement(value, document));
            return array;
        }

        private XmlElement GetString (string value, XmlDocument document)
        {
            XmlElement element = document.CreateElement("string");
            element.InnerText = value;
            return element;
        }

        private XmlElement GetBoolean (bool value, XmlDocument document)
        {
            if (value)
                return document.CreateElement("true");
            return document.CreateElement("false");
        }

        private XmlElement GetInteger (int value, XmlDocument document)
        {
            XmlElement element = document.CreateElement("integer");
            element.InnerText = value.ToString();
            return element;
        }

        private XmlElement GetReal (float value, XmlDocument document)
        {
            XmlElement element = document.CreateElement("real");
            element.InnerText = value.ToString();
            return element;
        }

        private XmlElement GetDate (DateTime date, XmlDocument document)
        {
            XmlElement element = document.CreateElement("date");
            element.InnerText = date.ToString();
            return element;
        }

        private XmlElement GetTimeSpan(TimeSpan span , XmlDocument document)
        {
            XmlElement element = document.CreateElement("timespan");
            element.InnerText = span.ToString();
            return element;
        }
        
        #endregion

        #region Loading
        private void LoadDocument (XmlDocument document)
        {
            XmlElement root = document.DocumentElement;
            dictionary = (Dictionary<string, object>)LoadDictionary(root.FirstChild);
        }

        private object LoadObject (XmlNode element)
        {
            if (element.Name.Equals("dict", StringComparison.OrdinalIgnoreCase))
                return LoadDictionary(element);
            else if (element.Name.Equals("array", StringComparison.OrdinalIgnoreCase))
                return LoadList(element);
            else if (element.Name.Equals("true", StringComparison.OrdinalIgnoreCase) || element.Name.Equals("false", StringComparison.OrdinalIgnoreCase))
                return LoadBoolean(element);
            else if (element.Name.Equals("integer", StringComparison.OrdinalIgnoreCase))
                return LoadInteger(element);
            else if (element.Name.Equals("real", StringComparison.OrdinalIgnoreCase))
                return LoadReal(element);
            else if (element.Name.Equals("date", StringComparison.OrdinalIgnoreCase))
                return LoadDate(element);
            else if (element.Name.Equals("timespan", StringComparison.OrdinalIgnoreCase))
                return LoadTimeSpan(element);
            else if (element.Name.Equals("null", StringComparison.OrdinalIgnoreCase))
                return null;
            return LoadString(element);
        }

        private object LoadDictionary (XmlNode element)
        {
            Dictionary<string, object> dict = new Dictionary<string, object>();
            for (int i = 0; i < element.ChildNodes.Count; i += 2)
            {
                dict[element.ChildNodes[i].InnerText] = LoadObject(element.ChildNodes[i + 1]);
            }
            return dict;
        }

        private object LoadList (XmlNode element)
        {
            List<object> array = new List<object>();
            foreach (XmlNode node in element.ChildNodes)
                array.Add(LoadObject(node));
            return array;
        }

        private object LoadString (XmlNode element)
        {
            return element.InnerText;
        }

        private object LoadBoolean (XmlNode element)
        {
            return (element.Name.Equals("true", StringComparison.OrdinalIgnoreCase));
        }

        private object LoadInteger (XmlNode element)
        {
            return Convert.ToInt32(element.InnerText);
        }

        private object LoadReal (XmlNode element)
        {
            return Convert.ToSingle(element.InnerText);
        }

        private object LoadDate(XmlNode element)
        {
            return DateTime.Parse(element.InnerText);
        }

        private object LoadTimeSpan(XmlNode element)
        {
            return TimeSpan.Parse(element.InnerText);
        }

        #endregion

        #region Helpers
        public IDictionary<string, object> DictionaryForKey (string key)
        {
            return (IDictionary<string, object>)this[key];
        }

        public IList<object> ArrayForKey (string key)
        {
            return (IList<object>)this[key];
        }

        public int IntegerForKey (string key)
        {
            return Convert.ToInt32(this[key]);
        }

        public float RealForKey (string key)
        {
            return Convert.ToSingle(this[key]);
        }

        public string StringForKey (string key)
        {
            return Convert.ToString(this[key]);
        }

        public bool BooleanForKey (string key)
        {
            return (StringForKey(key).Equals("true", StringComparison.OrdinalIgnoreCase));
        }

        public DateTime DateTimeForKey (string key)
        {
            return Convert.ToDateTime(this[key]);
        }

        #endregion

        #region IDictionary<string, object> Members
        public void Add (string key, object value)
        {
            dictionary.Add(key, value);
        }

        public bool ContainsKey (string key)
        {
            return dictionary.ContainsKey(key);
        }

        public ICollection<string> Keys
        {
            get { return ((IDictionary<string, object>)dictionary).Keys; }
        }

        public bool Remove (string key)
        {
            return dictionary.Remove(key);
        }

        public bool TryGetValue (string key, out object value)
        {
            return dictionary.TryGetValue(key, out value);
        }

        public ICollection<object> Values
        {
            get { return ((IDictionary<string, object>)dictionary).Values; }
        }

        public object this[string key]
        {
            get
            {
                object value = null;
                TryGetValue(key, out value);
                return value;
            }
            set { dictionary[key] = value; }
        }

        #endregion

        #region ICollection<KeyValuePair<string,object>> Members
        public void Add (KeyValuePair<string, object> item)
        {
            ((ICollection<KeyValuePair<string, object>>)dictionary).Add(item);
        }

        public void Clear ()
        {
            ((ICollection<KeyValuePair<string, object>>)dictionary).Clear();
        }

        public bool Contains (KeyValuePair<string, object> item)
        {
            return ((ICollection<KeyValuePair<string, object>>)dictionary).Contains(item);
        }

        public void CopyTo (KeyValuePair<string, object>[] array, int arrayIndex)
        {
            ((ICollection<KeyValuePair<string, object>>)dictionary).CopyTo(array, arrayIndex);
        }

        public int Count
        {
            get { return ((ICollection<KeyValuePair<string, object>>)dictionary).Count; }
        }

        public bool IsReadOnly
        {
            get { return ((ICollection<KeyValuePair<string, object>>)dictionary).IsReadOnly; }
        }

        public bool Remove (KeyValuePair<string, object> item)
        {
            return ((ICollection<KeyValuePair<string, object>>)dictionary).Remove(item);
        }
        #endregion

        #region IEnumerable<KeyValuePair<string,object>> Members
        public IEnumerator<KeyValuePair<string, object>> GetEnumerator ()
        {
            return ((IEnumerable<KeyValuePair<string, object>>)dictionary).GetEnumerator();
        }
        #endregion

        #region IEnumerable Members
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ()
        {
            return ((IEnumerable)dictionary).GetEnumerator();
        }
        #endregion
    }
}
